/* * This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT). * * Copyright (c) 2015 contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package cubicchunks.worldgen.generator.vanilla; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.EnumCreatureType; import net.minecraft.init.Blocks; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.Biome.SpawnListEntry; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.IChunkGenerator; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import net.minecraftforge.fml.common.registry.GameRegistry; import java.util.HashMap; import java.util.List; import java.util.Map; import cubicchunks.CubicChunks; import cubicchunks.util.Box; import cubicchunks.util.Coords; import cubicchunks.world.ICubicWorld; import cubicchunks.world.column.Column; import cubicchunks.world.cube.Cube; import cubicchunks.worldgen.generator.CubePrimer; import cubicchunks.worldgen.generator.ICubeGenerator; import cubicchunks.worldgen.generator.ICubePrimer; /** * A cube generator that tries to mirror vanilla world generation. Cubes in the normal world range will be copied from a * vanilla chunk generator, cubes above and below that will be filled with the most common block in the * topmost/bottommost layers. */ public class VanillaCompatibilityGenerator implements ICubeGenerator { private final int worldHeightBlocks; private final int worldHeightCubes; private IChunkGenerator vanilla; private ICubicWorld world; /** * Last chunk that was generated from the vanilla world gen */ private Chunk lastChunk; /** * We generate all the chunks in the vanilla range at once. This variable prevents infinite recursion */ private boolean optimizationHack; private Biome[] biomes; /** * Detected block for filling cubes below the world */ private IBlockState extensionBlockBottom = Blocks.STONE.getDefaultState(); /** * Detected block for filling cubes above the world */ private IBlockState extensionBlockTop = Blocks.AIR.getDefaultState(); /** * Create a new VanillaCompatibilityGenerator * * @param vanilla The vanilla generator to mirror * @param world The world in which cubes are being generated */ public VanillaCompatibilityGenerator(IChunkGenerator vanilla, ICubicWorld world) { this.vanilla = vanilla; this.world = world; // heuristics TODO: add a config that overrides this lastChunk = vanilla.provideChunk(0, 0); // lets scan the chunk at 0, 0 worldHeightBlocks = world.getActualHeight(); worldHeightCubes = worldHeightBlocks/Cube.SIZE; Map<IBlockState, Integer> blockHistogramBottom = new HashMap<>(); Map<IBlockState, Integer> blockHistogramTop = new HashMap<>(); for (int x = 0; x < Cube.SIZE; x++) { for (int z = 0; z < Cube.SIZE; z++) { // Scan three layers top / bottom each to guard against bedrock walls for (int y = 0; y < 3; y++) { IBlockState blockState = lastChunk.getBlockState(x, y, z); if (blockState.getBlock() == Blocks.BEDROCK) continue; // Never use bedrock for world extension int count = blockHistogramBottom.getOrDefault(blockState, 0); blockHistogramBottom.put(blockState, count + 1); } for (int y = worldHeightBlocks - 1; y > worldHeightBlocks - 4; y--) { IBlockState blockState = lastChunk.getBlockState(x, y, z); if (blockState.getBlock() == Blocks.BEDROCK) continue; // Never use bedrock for world extension int count = blockHistogramTop.getOrDefault(blockState, 0); blockHistogramTop.put(blockState, count + 1); } } } CubicChunks.LOGGER.debug("Block histograms: \nTop: " + blockHistogramTop + "\nBottom: " + blockHistogramBottom); int topcount = 0; for (Map.Entry<IBlockState, Integer> entry : blockHistogramBottom.entrySet()) { if (entry.getValue() > topcount) { extensionBlockBottom = entry.getKey(); topcount = entry.getValue(); } } CubicChunks.LOGGER.info("Detected filler block " + extensionBlockBottom.getBlock().getUnlocalizedName() + " " + "from layers [0, 2]"); topcount = 0; for (Map.Entry<IBlockState, Integer> entry : blockHistogramTop.entrySet()) { if (entry.getValue() > topcount) { extensionBlockTop = entry.getKey(); topcount = entry.getValue(); } } CubicChunks.LOGGER.info("Detected filler block " + extensionBlockTop.getBlock().getUnlocalizedName() + " from" + " layers [" + (worldHeightBlocks - 3) + ", " + (worldHeightBlocks - 1) + "]"); } @Override public void generateColumn(Column column) { this.biomes = this.world.getBiomeProvider() .getBiomes(this.biomes, Coords.cubeToMinBlock(column.getX()), Coords.cubeToMinBlock(column.getZ()), Cube.SIZE, Cube.SIZE); byte[] abyte = column.getBiomeArray(); for (int i = 0; i < abyte.length; ++i) { abyte[i] = (byte) Biome.getIdForBiome(this.biomes[i]); } } @Override public void recreateStructures(Column column) { vanilla.recreateStructures(column, column.getX(), column.getZ()); } @Override public ICubePrimer generateCube(int cubeX, int cubeY, int cubeZ) { CubePrimer primer = new CubePrimer(); if (cubeY < 0) { // Fill with bottom block for (int x = 0; x < Cube.SIZE; x++) { for (int y = 0; y < Cube.SIZE; y++) { for (int z = 0; z < Cube.SIZE; z++) { primer.setBlockState(x, y, z, extensionBlockBottom); } } } } else if (cubeY >= worldHeightCubes) { // Fill with top block for (int x = 0; x < Cube.SIZE; x++) { for (int y = 0; y < Cube.SIZE; y++) { for (int z = 0; z < Cube.SIZE; z++) { primer.setBlockState(x, y, z, extensionBlockTop); } } } } else { // Make vanilla generate a chunk for us to copy if (lastChunk.xPosition != cubeX || lastChunk.zPosition != cubeZ) { lastChunk = vanilla.provideChunk(cubeX, cubeZ); } if (!optimizationHack) { optimizationHack = true; // Recusrive generation for (int y = worldHeightCubes - 1; y >= 0; y--) { if (y == cubeY) { continue; } world.getCubeFromCubeCoords(cubeX, y, cubeZ); } optimizationHack = false; } // Copy from vanilla, replacing bedrock as appropriate ExtendedBlockStorage storage = lastChunk.getBlockStorageArray()[cubeY]; if (storage != null && !storage.isEmpty()) { for (int x = 0; x < Cube.SIZE; x++) { for (int y = 0; y < Cube.SIZE; y++) { for (int z = 0; z < Cube.SIZE; z++) { IBlockState state = storage.get(x, y, z); if (state == Blocks.BEDROCK.getDefaultState()) { if (y < Cube.SIZE/2) { primer.setBlockState(x, y, z, extensionBlockBottom); } else { primer.setBlockState(x, y, z, extensionBlockTop); } } else { primer.setBlockState(x, y, z, state); } } } } } } return primer; } @Override public void populate(Cube cube) { // Cubes outside this range are only filled with their respective block // No population takes place if (cube.getY() >= 0 && cube.getY() < worldHeightCubes) { for (int x = 0; x < 2; x++) { for (int z = 0; z < 2; z++) { for (int y = worldHeightCubes - 1; y >= 0; y--) { // Vanilla populators break the rules! They need to find the ground! world.getCubeFromCubeCoords(cube.getX() + x, y, cube.getZ() + z); } } } for (int y = worldHeightCubes - 1; y >= 0; y--) { // normal populators would not do this... but we are populating more than one cube! world.getCubeFromCubeCoords(cube.getX(), y, cube.getZ()).setPopulated(true); } vanilla.populate(cube.getX(), cube.getZ()); GameRegistry.generateWorld(cube.getX(), cube.getZ(), (World) world, vanilla, ((World) world).getChunkProvider()); } } @Override public Box getPopulationRequirement(Cube cube) { if (cube.getY() >= 0 && cube.getY() < worldHeightCubes) { return new Box( -1, 0 - cube.getY(), -1, 0, worldHeightCubes - cube.getY() - 1, 0 ); } return NO_POPULATOR_REQUIREMENT; } @Override public void recreateStructures(Cube cube) { } @Override public List<SpawnListEntry> getPossibleCreatures(EnumCreatureType creatureType, BlockPos pos) { return vanilla.getPossibleCreatures(creatureType, pos); } @Override public BlockPos getClosestStructure(String name, BlockPos pos) { return vanilla.getStrongholdGen((World) world, name, pos); } }